/* This is our agent class. It implements MASON's Steppable interface 
 * (which allow us to use MASON schedule to direct activations as well
 *  as extends JUNG's SparseVertex class, allowing for exploiting JUNG to 
 *  operate and display the connections network. */

package jungnetwork;

import java.util.Iterator;
import java.util.Set;

import sim.engine.SimState;
import sim.engine.Steppable;
import edu.uci.ics.jung.graph.impl.SparseGraph;
import edu.uci.ics.jung.graph.impl.SparseVertex;
import edu.uci.ics.jung.graph.impl.UndirectedSparseEdge;

public class NodeAgent extends SparseVertex implements Steppable {

    private static final long serialVersionUID = 1L;

    // Handle to simulation
    public PreferentialAttachment sim;

    // Number of activations left
    public int timer;

    // Handle to graph
    public SparseGraph graph;

    public NodeAgent(PreferentialAttachment attachment, int timer) {
        this.sim = attachment;
        this.timer = timer;
        this.graph = (SparseGraph) attachment.graph;
    }

    public void step(SimState state) {

        /*
         * Each time the agent is activated, it will either add or drop a single
         * connection to other agents. The probability of dropping is increasing
         * in number of connections agent already has, equals 1 when agent's
         * degree reaches maxDegree cap.
         */

        double rand = sim.random.nextDouble();

        if ((this.degree() / sim.maxDegree) < rand) {

            /*
             * Decision to establish a new connection has been made. Target node
             * will be selected with probability proportional to its degree
             * using Boltzmann transformation - implementing preferential
             * attachment logic.
             */

            double temperature = sim.temperature;

            SparseVertex gfrom = this;
            Set graphSet = graph.getVertices();
            Iterator i = graphSet.iterator();

            double verSum = 0;

            while (i.hasNext()) {

                verSum += Math.exp(temperature * ((SparseVertex) i.next()).degree());

                }

            double cumSum = 0;
            rand = state.random.nextDouble();
            i = graphSet.iterator();
            SparseVertex nextVertex;

            do {

                nextVertex = (SparseVertex) i.next();
                cumSum += Math.exp(temperature * nextVertex.degree());

                } while (i.hasNext() && (rand > (cumSum / verSum)));

            graph.addEdge(new UndirectedSparseEdge(gfrom, nextVertex));

            } else {

                // Cognitive capacity exhausted. A random edge will be removed.

                UndirectedSparseEdge toRemove = (UndirectedSparseEdge) this.getOutEdges().toArray()[state.random.nextInt(this.degree())];

                graph.removeEdge(toRemove);

                }

        // Connection operations have been finished. If it is still possible,
        // agent will register itself on the schedule for next activation.

        if (this.timer > 0) {
            this.timer--;
            sim.schedule.scheduleOnce(sim.schedule.time() - Math.log(sim.random.nextDouble()), this);
            }

    }

    }
